package com.bah.tract.service;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.regex.MatchResult;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.xml.ws.WebServiceContext;

import com.bah.tract.model.Person;
import com.bah.tract.model.Role;
import com.sun.xml.wss.XWSSecurityException;

public final class AuthenticationHelper {
	private AuthenticationHelper() {

	}

	public static com.bah.tract.model.Person getUser(WebServiceContext context,
			EntityManager em) throws TractServiceException_Exception {
		try {
			Subject sub = com.sun.xml.wss.SubjectAccessor
					.getRequesterSubject(context);
			// Metro would have stopped them at the door without a certificate.
			X500Principal principal = sub.getPrincipals(X500Principal.class)
					.iterator().next();
			X509Certificate publicCert = sub
					.getPublicCredentials(X509Certificate.class).iterator()
					.next();
			byte[] certData = publicCert.getEncoded();
			Query query = em
					.createQuery("select p from Person p where p.cn=?1");
			LdapName lName = new LdapName(
					principal.getName(X500Principal.RFC2253));
			String cn = lName.get(lName.size() - 1).substring(3);
			query.setParameter(1, cn);
			List<?> result = query.getResultList();
			if (result.size() == 1) {
				Person person = (Person) result.get(0);
				if (!Arrays.equals(person.getCertificate(), certData)) {
					throw new TractServiceException_Exception(
							"Certificate does not match.");
				}
				return person;
			} else {

				try {
					com.bah.tract.model.Person person = new com.bah.tract.model.Person();
					person.setCn(cn);
					person.setCertificate(certData);
					// Perform best effort to get userid, first, and last names
					Scanner scanner = new Scanner(cn);
					scanner.findInLine("(.+) (.+) (\\d+)");
					MatchResult mresult = scanner.match();
					person.setLastName(mresult.group(1));
					person.setFirstName(mresult.group(2));
					person.setBoozAllenID(Integer.parseInt(mresult.group(3)));
					Role userRole = em.find(Role.class, Long.valueOf(1));
					List<Role> roles = new ArrayList<Role>();
					roles.add(userRole);
					person.setRoles(roles);
					em.persist(person);
					return person;
				} catch (NumberFormatException e) {
					throw new TractServiceException_Exception(
							"Failed to create new user.", e);
				}

			}

		} catch (XWSSecurityException e) {
			throw new TractServiceException_Exception(
					"Failed to determine callers identity.", e);
		} catch (InvalidNameException e) {
			throw new TractServiceException_Exception(
					"Failed to create new user.", e);
		} catch (CertificateEncodingException e) {
			throw new TractServiceException_Exception(
					"Failed to determine callers identity.", e);
		}
	}

}
